package com.chen.sample1.web.exception;

import com.chen.sample1.common.constant.AliErrorCodeEnum;
import com.chen.sample1.common.message.ResponseMsg;
import com.chen.sample1.common.util.ExceptionUtil;
import io.jsonwebtoken.ExpiredJwtException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.validation.ConstraintViolationException;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Optional;

/**
 * 全局异常拦截处理器
 * @author ChenTian
 */
@ResponseBody
@ControllerAdvice
public class GlobalControllerExceptionHandler {
    private Logger logger = LoggerFactory.getLogger(getClass());


    /**
     * Controller上一层Web容器相关异常
     *
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler({
            NoHandlerFoundException.class,
            HttpMediaTypeNotSupportedException.class,
            MissingPathVariableException.class,
            TypeMismatchException.class,
            HttpMessageNotReadableException.class,
            HttpMessageNotWritableException.class,
            ServletRequestBindingException.class,
            ConversionNotSupportedException.class,
            MissingServletRequestPartException.class,
            AsyncRequestTimeoutException.class
    })
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseMsg handleServletException(Exception e) {
        logger.error("web container error:", e);
        return new ResponseMsg(AliErrorCodeEnum.USER_ERROR_0001, e.getMessage());
    }

    //405错误
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    @ExceptionHandler({HttpRequestMethodNotSupportedException.class})
    public ResponseMsg request405(HttpRequestMethodNotSupportedException e) {
        logger.error("web container error:", e);
        return new ResponseMsg(AliErrorCodeEnum.USER_ERROR_0001, e.getMessage());
    }

    //406错误
    @ResponseStatus(HttpStatus.NOT_ACCEPTABLE)
    @ExceptionHandler({HttpMediaTypeNotAcceptableException.class})
    public ResponseMsg request406(HttpMediaTypeNotAcceptableException e) {
        logger.error("web container error:", e);
        return new ResponseMsg(AliErrorCodeEnum.USER_ERROR_0001, e.getMessage());
    }

    /**
     * 参数校验异常，缺少参数异常
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public ResponseMsg handleBindException(MissingServletRequestParameterException e) {
        logger.error("缺少参数异常：", e);
        String msg = MessageFormat.format("缺少参数{0}", (e.getParameterName()));
        return new ResponseMsg(AliErrorCodeEnum.USER_ERROR_0001, msg);
    }

    /**
     * 参数校验异常
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = ConstraintViolationException.class)
    public ResponseMsg handleValidException(ConstraintViolationException e) {
        logger.error("参数绑定校验异常：", e);
        return new ResponseMsg(AliErrorCodeEnum.USER_ERROR_0001,  "参数含有特殊字符："+e.getMessage());
    }

    /**
     * 参数校验异常，参数绑定异常
     *
     * @param e 异常
     * @return 异常结果
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = BindException.class)
    public ResponseMsg handleBindException(BindException e) {
        logger.error("参数绑定校验异常：", e);
        return wrapperBindingResult(e.getBindingResult());
    }

    /**
     * 参数校验异常，将校验失败的所有异常组合成一条错误信息
     *
     * @param e 异常
     * @return 异常结果
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public ResponseMsg handleValidException(MethodArgumentNotValidException e) {
        logger.error("方法参数不合法异常：", e);
        return wrapperBindingResult(e.getBindingResult());
    }

    /**
     * token异常处理方法
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ExpiredJwtException.class)
    public ResponseMsg unauthorized(ExpiredJwtException e) {
        logger.info("token过期：{}", e);
        return new ResponseMsg(AliErrorCodeEnum.USER_ERROR_A0230, e.getMessage());
    }

    //栈溢出
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler({StackOverflowError.class})
    public ResponseMsg stackOverflow(StackOverflowError e) {
        logger.error("system error:", e);
        return new ResponseMsg(AliErrorCodeEnum.SYSTEM_ERROR_B0001, e.getMessage());
    }

    //除数不能为0
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler({ArithmeticException.class})
    public ResponseMsg arithmeticException(ArithmeticException e) {
        logger.error("system error:", e);
        return new ResponseMsg(AliErrorCodeEnum.SYSTEM_ERROR_B0001, e.getMessage());
    }

    //空指针异常
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(NullPointerException.class)
    public ResponseMsg nullPointerExceptionHandler(NullPointerException e) {
        logger.error("system error:", e);
        return new ResponseMsg(AliErrorCodeEnum.SYSTEM_ERROR_B0001, e.getMessage());
    }

    //IO异常
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(IOException.class)
    public ResponseMsg iOExceptionHandler(IOException e) {
        logger.error("system error:", e);
        return new ResponseMsg(AliErrorCodeEnum.SYSTEM_ERROR_B0001, e.getMessage());
    }

    //未知方法异常
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(NoSuchMethodException.class)
    public ResponseMsg noSuchMethodExceptionHandler(NoSuchMethodException e) {
        logger.error("system error:", e);
        return new ResponseMsg(AliErrorCodeEnum.SYSTEM_ERROR_B0001, e.getMessage());
    }

    //数组越界异常
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(IndexOutOfBoundsException.class)
    public ResponseMsg indexOutOfBoundsExceptionHandler(IndexOutOfBoundsException e) {
        logger.error("system error:", e);
        return new ResponseMsg(AliErrorCodeEnum.SYSTEM_ERROR_B0001, e.getMessage());
    }

    //运行时异常
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(RuntimeException.class)
    public ResponseMsg runtimeExceptionHandler(RuntimeException e) {
        logger.error("system error:", e);
        return new ResponseMsg(AliErrorCodeEnum.SYSTEM_ERROR_B0001, e.getMessage());
    }

    /**
     * 全局异常捕捉处理
     */
    @ExceptionHandler(value = Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ResponseMsg errorHandler(Exception e) {
        logger.error("system error:", e);
        return new ResponseMsg(AliErrorCodeEnum.SYSTEM_ERROR_B0001, e.getMessage(), ExceptionUtil.getStackTrace(e));
    }

    /**
     * 包装绑定异常结果
     *
     * @param bindingResult 绑定结果
     * @return 异常结果
     */
    private ResponseMsg wrapperBindingResult(BindingResult bindingResult) {
        StringBuilder msg = new StringBuilder();
        for (ObjectError error : bindingResult.getAllErrors()) {
            msg.append(", ");
            if (error instanceof FieldError) {
                msg.append(((FieldError) error).getField()).append(": ");
            }
            msg.append(Optional.ofNullable(error.getDefaultMessage()).orElse(""));

        }

        return new ResponseMsg(AliErrorCodeEnum.USER_ERROR_0001, msg.substring(2));
    }

}
